/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package simhya.dataprocessing;
import simhya.dataprocessing.chart.*;
import simhya.model.store.Store;
import simhya.model.flat.FlatModel;
import java.util.ArrayList;
import javax.swing.JFrame;
//import simhya.GlobalOptions;

import simhya.dataprocessing.chart.Plot2DTrajectory;

/**
 *
 * @author Luca
 */
public abstract class DataCollector {
    
    ArrayList<Trajectory> trajectories;
    private int currentTrajectoryId;
    protected Trajectory currentTrajectory;

    boolean saveByStep;
    boolean saveOnlyFinalState;
    double deltaTime;
    double lastPrintTime;
    int deltaStep;
    long lastPrintStep;
    private int expectedPoints;
    double finalTime;

    private boolean initialized;

    double initialSavingTime;
   
    protected Store store;

    String modelName;

    private ArrayList<String> varsToBePrinted;
    private ArrayList<String> paramsToBePrinted;

    private ArrayList<String> varsToSave;
    private ArrayList<String> expVarsToSave;




    public boolean fullLatexDocument = false;

    
    
    public DataCollector(FlatModel model) {
        this.store = model.getStore();
        this.modelName  = model.getName();
        this.trajectories = new ArrayList<Trajectory>();
        currentTrajectoryId = -1;
        currentTrajectory = null;
        saveByStep = false;
        saveOnlyFinalState = false;
        deltaTime = 1;
        lastPrintTime = 0;
        deltaStep = 1;
        lastPrintStep = 0;
        expectedPoints = 0;
        initialSavingTime = 0;
        finalTime = 0;
        varsToBePrinted = null;
        paramsToBePrinted = null;
        varsToSave = null;
        expVarsToSave = null;
        initialized = false;
    }


    public void setVarsToBeSaved(ArrayList<String> varsToSave) {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        this.varsToSave = new ArrayList<String>();
        this.expVarsToSave = new ArrayList<String>();
        ArrayList<String> vars = store.getNameOfAllVariables();
        ArrayList<String> expVars = store.getNameOfAllExpressionVariables();

        for (String n : varsToSave) {
            if (vars.contains(n))
                this.varsToSave.add(n);
            else if (expVars.contains(n))
                this.expVarsToSave.add(n);
            else
                throw new DataException("Variable " + n + " is not defined");
        }
    }
    public void saveAllVariables() {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        varsToSave = null;
        expVarsToSave = null;
    }

   

    public void setPrintConditionByTime(int points, double finalTime) {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        if (points <= 0 )
            throw new DataException("Number of points must be positive");
        this.deltaTime  = finalTime/points;
        this.saveByStep = false;
        initialSavingTime = 0;
        this.expectedPoints = points + 1;
        this.initialized = true;
        this.finalTime = finalTime;
    }

    public void setPrintConditionByTime(double burnout, int points, double finalTime) {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        if (points <= 0 )
            throw new DataException("Number of points must be positive");
        this.deltaTime  = (finalTime-burnout)/points;
        this.saveByStep = false;
        initialSavingTime = burnout;
        this.expectedPoints = points + 1;
        this.initialized = true;
        this.finalTime = finalTime;
    }
   

    public void setPrintConditionByTime(double burnout, double step) {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        if (step <= 0 )
            throw new DataException("Print step must be positive");
        this.deltaTime  = step;
        this.saveByStep = false;
        initialSavingTime = burnout;
        finalTime = burnout - 1;
        this.expectedPoints = -1;
        this.initialized = true;
    }

    
    public void setPrintConditionByTime(double burnout, double step, double finalTime) {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        if (step <= 0 )
            throw new DataException("Step must be positive");
        this.deltaTime  = step;
        this.saveByStep = false;
        initialSavingTime = burnout;
        computeExpectedNumberOfPoints(finalTime);
        this.initialized = true;
        this.finalTime = finalTime;
    }

   
    public void setPrintConditionByStep(int step) {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        if (step <= 0 )
            throw new DataException("Print step must be positive");
        this.deltaStep = step;
        this.saveByStep = true;
        this.expectedPoints = -1;
        this.initialSavingTime = 0;
        this.initialized = true;
    }


    public void saveOnlyFinalState() {
        if (initialized)
            throw new DataException("Data collector must be configured before generating the first trajectory");
        this.saveOnlyFinalState = true;
        this.initialized = true;
        this.expectedPoints = 0;
    }
    
    private void computeExpectedNumberOfPoints(double finalTime) {
            long x = (long)Math.ceil((finalTime-initialSavingTime)/deltaTime);
            if (x <= 0 || x >= Integer.MAX_VALUE)
                throw new DataException("Too many data points, please increase the step size or reduce the final time");
            this.expectedPoints = (int)x + 1;
    }
    
    
    public boolean dataNeeded(double nextTime) {
        if (this.saveOnlyFinalState)
            return false;
        if (nextTime > this.lastPrintTime + this.deltaTime)
            return true;
        else return false;
    }
    public boolean dataNeeded(double nextTime,long stepNumber) {
        if (this.saveOnlyFinalState)
            return false;
        if (this.saveByStep && stepNumber == this.lastPrintStep + this.deltaStep)
            return true;
        else if (nextTime > this.lastPrintTime + this.deltaTime)
            return true;
        else return false;
    }

    //public double nextTimeOfDataPoint() {}

    //public void putData() {}
    public void putData(double nextTime) {
        if (this.saveByStep) {
            currentTrajectory.add(nextTime, store.getVariablesValues(), store.getExpressionVariablesValues());
            this.lastPrintStep += this.deltaStep;
        }
        else
            while (this.lastPrintTime + this.deltaTime < nextTime) {
                lastPrintTime += deltaTime;
                currentTrajectory.add(lastPrintTime, store.getVariablesValues(), store.getExpressionVariablesValues());
            }
    }
    public void putFinalState(double time, long steps) {
        if (this.saveByStep || this.saveOnlyFinalState) {
            this.currentTrajectory.addFinalState(time, store.getVariablesValues(), store.getExpressionVariablesValues());
            this.currentTrajectory.totalSteps = steps;
        }
        else {
            if (finalTime > this.initialSavingTime) {
                while (this.lastPrintTime + this.deltaTime <= finalTime) {
                    lastPrintTime += deltaTime;
                    currentTrajectory.add(lastPrintTime, store.getVariablesValues(), store.getExpressionVariablesValues());
                }
            }
            this.currentTrajectory.addFinalState(time, store.getVariablesValues(), store.getExpressionVariablesValues());
            this.currentTrajectory.totalSteps = steps;
        }

        this.currentTrajectory.addFinalState(time, store.getVariablesValues(), store.getExpressionVariablesValues());
    }
    
    public Trajectory getTrajectory(int id) {
        if (id < 0 && id > this.currentTrajectoryId)
            throw new DataException("Trajectory " + id + " does not exists");
        return this.trajectories.get(id);
    }

    public void newTrajectory() {
        if (!initialized)
            throw new DataException("Print condition must be initialized first");
        this.currentTrajectoryId++;
        Trajectory t;
        if (expectedPoints >= 0)
            if (this.varsToSave != null)
                t = new Trajectory(currentTrajectoryId,expectedPoints,store,varsToSave,expVarsToSave);
            else
                t = new Trajectory(currentTrajectoryId,expectedPoints,store);
        else
            if (this.varsToSave != null)
                t = new Trajectory(currentTrajectoryId,store,varsToSave,expVarsToSave);
            else
                t = new Trajectory(currentTrajectoryId,store);
        this.trajectories.add(t);
        this.currentTrajectory = t;
        this.lastPrintStep = -this.deltaStep;
        this.lastPrintTime = this.initialSavingTime - this.deltaTime;
    }

    public void clearAllTrajectories() {
        this.trajectories  = new ArrayList<Trajectory>();
        this.currentTrajectoryId = -1;
        this.currentTrajectory = null;
    }


    public void setVarsToBePrinted(ArrayList<String> vars) {
        this.varsToBePrinted = vars;
    }

    public void setAllVarsToBePrinted() {
        this.varsToBePrinted = null;
    }

    public void setParamsToBePrinted(ArrayList<String> pars) {
        this.paramsToBePrinted = pars;
    }

    public void setNoParamToBePrinted() {
        this.paramsToBePrinted = null;
    }


    
    public void saveSingleTrajectoryToCSV(int trajectory, String filename) {
        int n = this.trajectories.size();
        if (trajectory < 0 || trajectory >= n)
            throw new DataException("There is no trajectory with id " + trajectory);
        Trajectory t = this.trajectories.get(trajectory);
        if (varsToBePrinted != null)
            t.setVariablesToPrint(varsToBePrinted);
        if (paramsToBePrinted != null)
            t.setParametersToPrint(paramsToBePrinted);
        boolean saveFinalState = (finalTime < this.initialSavingTime) || this.saveByStep || this.saveOnlyFinalState;
        t.saveAsCSV(filename, false, false, saveFinalState );
    }

    public void saveAllTrajectoriesToCSV(String filename, boolean printRunNumber) {
        int n = this.trajectories.size();
        if (n == 0)
            throw new DataException("There is no trajectory");
        boolean saveFinalState = (finalTime < this.initialSavingTime) || this.saveByStep || this.saveOnlyFinalState;
        for (int i=0;i<n;i++) {
            Trajectory t = this.trajectories.get(i);
            if (varsToBePrinted != null)
                t.setVariablesToPrint(varsToBePrinted);
            if (paramsToBePrinted != null)
                t.setParametersToPrint(paramsToBePrinted);
            t.saveAsCSV(filename, (i == 0 ? false : true), (n == 1? false : printRunNumber), saveFinalState);
        }
    }


    public void saveAllTrajectoriesToCSV(String filename, boolean printRunNumber, boolean append) {
        int n = this.trajectories.size();
        if (n == 0)
            throw new DataException("There is no trajectory");
        boolean saveFinalState = (finalTime < this.initialSavingTime) || this.saveByStep || this.saveOnlyFinalState;
        for (int i=0;i<n;i++) {
            Trajectory t = this.trajectories.get(i);
            if (varsToBePrinted != null)
                t.setVariablesToPrint(varsToBePrinted);
            if (paramsToBePrinted != null)
                t.setParametersToPrint(paramsToBePrinted);
            t.saveAsCSV(filename, (i == 0 ? append : true), (n == 1? false : printRunNumber),saveFinalState);
        }
    }


    private LineChart plotTrajectories(ArrayList<Integer> trajIds) {
        
        ArrayList<Plot2DTrajectory> plotList = new ArrayList<Plot2DTrajectory>();
        for (Integer i : trajIds) {
            Trajectory t = this.trajectories.get(i);
            if (varsToBePrinted != null)
                t.setVariablesToPrint(varsToBePrinted);
            plotList.addAll(t.getTrajectoriesToPlot());
        }
        LineChart chart = new LineChart(plotList,"time","values");

        chart.fullLatexDocument = this.fullLatexDocument;
        return chart;
    }

    /**
     * Generates a {@link Line2DChart} from  a list of trajectory ids.
     * @param trajIds a list of trjectory ids
     * @return a Line2Dchart (chart using JMathPlot)
     */
    private Line2DChart plot2DTrajectories(ArrayList<Integer> trajIds) {
        ArrayList<Plot2DTrajectory> plotList = new ArrayList<Plot2DTrajectory>();
        for (Integer i : trajIds) {
            Trajectory t = this.trajectories.get(i);
            if (varsToBePrinted != null)
                t.setVariablesToPrint(varsToBePrinted);
            plotList.addAll(t.getTrajectoriesToPlot());
        }
        Line2DChart chart = new Line2DChart(plotList,"time","values");
        return chart;
    }

    public void plotSingleTrajectory(int id, String filename, PlotFileType target) {
        int n = this.trajectories.size();
        if (id < 0 || id >= n)
            throw new DataException("There is no trajectory with id " + id);
        ArrayList<Integer> traj = new ArrayList<Integer>();
        traj.add(id);
        LineChart chart = plotTrajectories(traj);
        chart.saveToFile(filename,target);
    }

    public void plotAllTrajectories(String filename, PlotFileType target) {
        int n = this.trajectories.size();
        if (n == 0) throw new DataException("There is no trajectory");
        ArrayList<Integer> traj = new ArrayList<Integer>();
        for (int i=0;i<n;i++) traj.add(i);
        LineChart chart = plotTrajectories(traj);
        chart.saveToFile(filename,target);
    }

    public void plotTrajectories(int first, int last, String filename, PlotFileType target) {
        int n = this.trajectories.size();
        if (n == 0) throw new DataException("There is no trajectory");
        if (first >= n) throw new DataException("Invalid first trajectory id");
        if (last < first) throw new DataException("Invalid last trajectory id");
        if (last >= n) last  = n-1;
        if (first < 0) first  = 0;
        ArrayList<Integer> traj = new ArrayList<Integer>();
        for (int i=first;i<=last;i++) traj.add(i);
        LineChart chart = plotTrajectories(traj);
        chart.saveToFile(filename,target);
    }

    public void plotTrajectories(ArrayList<Integer> trajIds, String filename, PlotFileType target) {
        int n = this.trajectories.size();
        if (n == 0) throw new DataException("There is no trajectory");
        for (int i=0;i<trajIds.size();i++)
            if (trajIds.get(i) < 0 || trajIds.get(i)>=n)
                throw new DataException("Invalid trajectory id");
        if (trajIds.isEmpty()) throw new DataException("Empty trajectory list");
        LineChart chart = plotTrajectories(trajIds);
        chart.saveToFile(filename,target);
    }


    
    public JFrame plotSingleTrajectoryToScreen(int id) {
        int n = this.trajectories.size();
        if (id < 0 || id >= n)
            throw new DataException("There is no trajectory with id " + id);
        ArrayList<Integer> traj = new ArrayList<Integer>();
        traj.add(id);
        LineChart chart = plotTrajectories(traj);
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " trajectory " + id);
        return frame;
    }

    public JFrame plotAllTrajectoriesToScreen() {
        int n = this.trajectories.size();
        if (n == 0) throw new DataException("There is no trajectory");
        ArrayList<Integer> traj = new ArrayList<Integer>();
        for (int i=0;i<n;i++) traj.add(i);
        
        LineChart chart = plotTrajectories(traj);
        //testing new 
//        Line2DChart chart = plot2DTrajectories(traj);
        //chart.removeLegend();
        
        
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " all trajectories");
        return frame;
    }

    public JFrame plotTrajectoriesToScreen(int first, int last) {
        int n = this.trajectories.size();
        if (n == 0) throw new DataException("There is no trajectory");
        if (first >= n) throw new DataException("Invalid first trajectory id");
        if (last < first) throw new DataException("Invalid last trajectory id");
        if (last >= n) last  = n-1;
        if (first < 0) first  = 0;
        ArrayList<Integer> traj = new ArrayList<Integer>();
        for (int i=first;i<=last;i++) traj.add(i);
        LineChart chart = plotTrajectories(traj);
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " trajectories " + first + " to " + last);
        return frame;
    }

    public JFrame plotTrajectoriesToScreen(ArrayList<Integer> trajIds) {
        int n = this.trajectories.size();
        if (n == 0) throw new DataException("There is no trajectory");
        for (int i=0;i<trajIds.size();i++)
            if (trajIds.get(i) < 0 || trajIds.get(i)>=n)
                throw new DataException("Invalid trajectory id");
        if (trajIds.isEmpty()) throw new DataException("Empty trajectory list");
        LineChart chart = plotTrajectories(trajIds);
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " selected trajectories");
        return frame;
    }
    
    /**
     * constructs a list of {@link Plot2DTrajectory} from all trajectories for the two given variables
     * @param var1
     * @param var2
     * @return 
     */
    private ArrayList<Plot2DTrajectory> getPhasePlaneTrajectories(String var1, String var2) {
        ArrayList<Plot2DTrajectory> list = new ArrayList<Plot2DTrajectory>();
        for (Trajectory t : this.trajectories) {
            Plot2DTrajectory pt = new Plot2DTrajectory(t.savedPoints);
            pt.name = var1 + "--" + var2;
            int id1 = t.getVarId(var1); //throws dataexception if not present
            int id2 = t.getVarId(var2);
            for (int i=0;i<t.savedPoints;i++) {
                pt.x[i] = t.data[id1][i];
                pt.y[i] = t.data[id2][i];
            }
            list.add(pt);
        }
        return list;
    }
    
    /**
     * constructs a list of {@link Plot2DTrajectory} from a subset of trajectories for the two given variables
     * @param var1
     * @param var2
     * @param ids a list of trajectory ids
     * @return 
     */
    private ArrayList<Plot2DTrajectory> getPhasePlaneTrajectories(String var1, String var2, int... ids) {
        ArrayList<Plot2DTrajectory> list = new ArrayList<Plot2DTrajectory>();
        for (int j=0;j<ids.length;j++) {
            Trajectory t = this.trajectories.get(ids[j]);
            Plot2DTrajectory pt = new Plot2DTrajectory(t.savedPoints);
            pt.name = var1 + "--" + var2;
            int id1 = t.getVarId(var1); //throws dataexception if not present
            int id2 = t.getVarId(var2);
            for (int i=0;i<t.savedPoints;i++) {
                pt.x[i] = t.data[id1][i];
                pt.y[i] = t.data[id2][i];
            }
            list.add(pt);
        }
        return list;
    }
    
    
     /**
     * constructs a list of {@link Plot3DTrajectory} from all trajectories for the three given variables
     * @param var1
     * @param var2
     * @param var3
     * @return 
     */
    private ArrayList<Plot3DTrajectory> getPhaseSpaceTrajectories(String var1, String var2, String var3) {
        ArrayList<Plot3DTrajectory> list = new ArrayList<Plot3DTrajectory>();
        for (Trajectory t : this.trajectories) {
            Plot3DTrajectory pt = new Plot3DTrajectory(t.savedPoints);
            pt.name = var1 + "--" + var2 + "--" + var3;
            int id1 = t.getVarId(var1); //throws dataexception if not present
            int id2 = t.getVarId(var2);
            int id3 = t.getVarId(var2);
            for (int i=0;i<t.savedPoints;i++) {
                pt.x[i] = t.data[id1][i];
                pt.y[i] = t.data[id2][i];
                pt.z[i] = t.data[id3][i];
            }
            list.add(pt);
        }
        return list;
    }
    
    
    /**
     * constructs a list of {@link Plot3DTrajectory} from a subset of trajectories for the three given variables
     * @param var1
     * @param var2
     * @param var3
     * @param ids a list of trajectory ids
     * @return 
     */
    private ArrayList<Plot3DTrajectory> getPhaseSpaceTrajectories(String var1, String var2, String var3, int... ids) {
        ArrayList<Plot3DTrajectory> list = new ArrayList<Plot3DTrajectory>();
        for (int j=0;j<ids.length;j++) {
            Trajectory t = this.trajectories.get(ids[j]);
            Plot3DTrajectory pt = new Plot3DTrajectory(t.savedPoints);
            pt.name = var1 + "--" + var2 + "--" + var3;
            int id1 = t.getVarId(var1); //throws dataexception if not present
            int id2 = t.getVarId(var2);
            int id3 = t.getVarId(var2);
            for (int i=0;i<t.savedPoints;i++) {
                pt.x[i] = t.data[id1][i];
                pt.y[i] = t.data[id2][i];
                pt.z[i] = t.data[id3][i];
            }
            list.add(pt);
        }
        return list;
    }
    
    public JFrame phasePlanePlotToScreen(String var1, String var2, int... ids) {
        ArrayList<Plot2DTrajectory> traj = this.getPhasePlaneTrajectories(var1, var2, ids);
        LineChart chart = new LineChart(traj,var1,var2);
        chart.removeLegend();
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " phase plane " + var1 + "--" + var2);
        return frame;
    }
    
    public JFrame phasePlanePlotToScreen(String var1, String var2) {
        ArrayList<Plot2DTrajectory> traj = this.getPhasePlaneTrajectories(var1, var2);
        LineChart chart = new LineChart(traj,var1,var2);
        chart.removeLegend();
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " phase plane " + var1 + "--" + var2);
        return frame;
    }
    
    public void phasePlanePlot(String var1, String var2, String filename, PlotFileType target, int... ids) {
        ArrayList<Plot2DTrajectory> traj = this.getPhasePlaneTrajectories(var1, var2, ids);
        LineChart chart = new LineChart(traj,var1,var2);
        chart.removeLegend();
        chart.saveToFile(filename, target);
    }
    
    public void phasePlanePlot(String var1, String var2, String filename, PlotFileType target) {
        ArrayList<Plot2DTrajectory> traj = this.getPhasePlaneTrajectories(var1, var2);
        LineChart chart = new LineChart(traj,var1,var2);
        chart.removeLegend();
        chart.saveToFile(filename, target);
    }
    
    public JFrame phaseSpacePlotToScreen(String var1, String var2, String var3, int... ids) {
        ArrayList<Plot3DTrajectory> traj = this.getPhaseSpaceTrajectories(var1, var2, var3, ids);
        Line3DChart chart = new Line3DChart(traj,var1,var2,var3);
        chart.removeLegend();
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " phase space " + var1 + "--" + var2 + "--" + var3);
        return frame;
    }
    
    public JFrame phaseSpacePlotToScreen(String var1, String var2, String var3) {
        ArrayList<Plot3DTrajectory> traj = this.getPhaseSpaceTrajectories(var1, var2, var3);
        Line3DChart chart = new Line3DChart(traj,var1,var2,var3);
        chart.removeLegend();
        JFrame frame = chart.show();
        frame.setTitle("SimHyA: " + this.modelName + " phase space " + var1 + "--" + var2 + "--" + var3);
        return frame;
    }
    
    public void phaseSpacePlot(String var1, String var2, String var3, String filename, PlotFileType target, int... ids) {
        ArrayList<Plot3DTrajectory> traj = this.getPhaseSpaceTrajectories(var1, var2, var3, ids);
        Line3DChart chart = new Line3DChart(traj,var1,var2,var3);
        chart.removeLegend();
        chart.saveToFile(filename, target);
    }
    
    public void phaseSpacePlot(String var1, String var2, String var3, String filename, PlotFileType target) {
        ArrayList<Plot3DTrajectory> traj = this.getPhaseSpaceTrajectories(var1, var2, var3);
        Line3DChart chart = new Line3DChart(traj,var1,var2,var3);
        chart.removeLegend();
        chart.saveToFile(filename, target);
    }


  

}
